#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@File ：__init__.py
@Author ：zqy
@Email : zqingy@work@163.com
@note: 参数校验
"""
import ast
import re
from abc import ABC, abstractmethod
from datetime import datetime, date, timedelta
from decimal import Decimal
from email.policy import default
from typing import Union, Any, List, Dict

import phonenumbers
from flask_restx import reqparse
from flask_restx.reqparse import RequestParser
from werkzeug.exceptions import BadRequest

from application import consts

__all__ = ["BaseValidator", "with_parser", "GETRequestParser", "JSONRequestParser"]


class CUDValidator(ABC):
    """
    增删改通用校验器
    """

    @classmethod
    @abstractmethod
    def get(cls, *args, **kwargs): ...

    @classmethod
    @abstractmethod
    def post(cls, *args, **kwargs): ...

    @classmethod
    @abstractmethod
    def put(cls, *args, **kwargs): ...


class BaseRequestParser(RequestParser):
    """
    自定义参数验证器基类, 禁止在此处的方法中要求某个参数值必填,
    如果需要修改方法或非通用的方法, 请使用继承的方式进行重写
    """

    def parse_args(self, *args, **kwargs):
        """
        Parse arguments and handle errors.
        """
        try:
            args = super().parse_args(*args, **kwargs)
        except BadRequest as e:
            # Custom error handling can be added here if necessary
            msg = "\n".join(getattr(e, "data", {}).get("errors", {}).values())
            from application import BasicException, StatusCodeEnum
            raise BasicException(status_code=StatusCodeEnum.VALIDATOR_ERROR.value, error_message=msg)

        return args


class GETRequestParser(BaseRequestParser):
    def add_argument(self, *args, **kwargs):
        repetition = {'location'} & kwargs.keys()
        assert not repetition, f"重复的参数：{repetition}"
        assert len(args) > 0, "位置参数为空"
        return super(GETRequestParser, self).add_argument(*args, **kwargs, location="args")

    def init_page(self):
        self.add_argument("current_page", type=int, default=1, help="页码")
        self.add_argument("page_size", type=int, default=10, help="每页数量")


class JSONRequestParser(BaseRequestParser):
    def add_argument(self, *args, **kwargs):
        repetition = {'location'} & kwargs.keys()
        assert not repetition, f"重复的参数：{repetition}"
        assert len(args) > 0, "位置参数为空"
        return super(JSONRequestParser, self).add_argument(*args, **kwargs, location="json")

    def add_argument_non_null(self, *args, **kwargs):
        """
        如果有传该参数，则校验不为NULL
        :param args:
        :param kwargs:
        :return:
        """
        repetition = {'nullable', 'location'} & kwargs.keys()
        assert not repetition, f"重复的参数：{repetition}"
        assert len(args) > 0, "位置参数为空"
        return super(JSONRequestParser, self).add_argument(*args, **kwargs, nullable=False, location="json")

    def add_argument_required(self, *args, **kwargs):
        """
        校验是否有该参数，不判断其值是否为NULL
        :param args:
        :param kwargs:
        :return:
        """
        repetition = {'required', 'location'} & kwargs.keys()
        assert not repetition, f"重复的参数：{repetition}"
        assert len(args) > 0, "位置参数为空"
        return super(JSONRequestParser, self).add_argument(*args, **kwargs, required=True, location="json")

    def add_argument_required_non_null(self, *args, **kwargs):
        """
        校验参数必填，且不允许为NULL
        :param args:
        :param kwargs:
        :return:
        """
        repetition = {'required', 'nullable', 'location'} & kwargs.keys()
        assert not repetition, f"重复的参数：{repetition}"
        assert len(args) > 0, "位置参数为空"
        return super(JSONRequestParser, self).add_argument(*args, **kwargs, required=True, nullable=False,
                                                           location="json")


class BaseValidator(CUDValidator):
    """
    自定义参数验证器基类, 禁止在此处的方法中要求某个参数值必填,
    如果需要修改方法或非通用的方法, 请使用继承的方式创建与业务相关的子类
    """

    @classmethod
    def get(cls):
        p = GETRequestParser(trim=True)
        p.add_argument("current_page", type=int, default=1, help="页码")
        p.add_argument("page_size", type=int, default=10, help="每页条数")
        return p

    @classmethod
    def post(cls):
        return JSONRequestParser(trim=True)

    @classmethod
    def put(cls):
        return cls.post()

    @staticmethod  # 电话号码, 强校验格式, 并自动格式化值
    def phone_number(value: str, valid: bool = False) -> str:
        value = value.strip()
        if not value:
            return ""
        try:
            v = phonenumbers.parse(value, "CN")  # 默认用 "CN"，不让用户必须输入 +86
        except Exception:
            raise AttributeError("请输入有效的中国大陆电话号码，例如: 138****1234")

        if valid:
            assert phonenumbers.is_valid_number(v), "请输入有效的电话号码，例如: 138****1234"
        else:
            assert phonenumbers.is_possible_number(v), "请输入格式正确的电话号码，例如: 138****1234"

            # 格式化成本地格式（只返回纯手机号）
        national_number = phonenumbers.format_number(v, phonenumbers.PhoneNumberFormat.NATIONAL)
        # 去掉空格，比如 138 1234 5678 => 13812345678
        national_number = national_number.replace(' ', '')

        return national_number

    @staticmethod  # 验证密码强度，要求至少包含大写字母，小写字母，数字
    def password(pwd: str) -> str:
        pwd = pwd.strip()
        if not pwd:
            return ""
        assert len(pwd) >= 6, "密码强度较弱，请至少输入6个字符"
        has_number, has_upper, has_lower = False, False, False
        for c in pwd:
            if c.isnumeric():
                has_number = True
                continue
            if c.isupper():
                has_upper = True
                continue
            if c.islower():
                has_lower = True
        assert has_number, "密码强度较弱，请至少包含一个数字"
        assert has_upper, "密码强度较弱，请至少包含一个大写字母"
        assert has_lower, "密码强度较弱，请至少包含一个小写字母"
        return pwd

    @staticmethod  # 邮箱
    def email(value: str) -> str:
        if not value:
            return ""
        value = value.strip()
        if value == "":
            return value
        assert re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", value), "请输入有效的邮箱账号"
        return value

    @staticmethod  # 性别
    def gender(value: str) -> str:
        if not value:
            return "未知"
        assert value in ["男", "女", "未知"], "不支持的性别选项"
        return value

    @staticmethod  # 将前端传递的CST时间解析为UTC时间对象
    def utc_datetime(value: str) -> Union[datetime, None]:
        if not value:
            return None
        value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S").astimezone(consts.TZ_UTC)
        return value

    @staticmethod  # 将前端日期解析为时间对象
    def original_date(value: str) -> Union[date, None]:
        if not value:
            return None
        value = datetime.strptime(value, "%Y-%m-%d").date()
        return value

    @staticmethod  # 将前端传的月份，固定补全为该月的一号
    def original_month(value: str) -> Union[date, None]:
        if not value:
            return None
        value = datetime.strptime(f"{value}-01", "%Y-%m-%d").date()
        return value

    @staticmethod  # 将前端传的月份，固定补全为该月的一号
    def original_month_str(value: str) -> Union[str, None]:
        if not value:
            return None
        value = f"{value}-01"
        return value

    @staticmethod  # 将前端日期解析为时间对象
    def required_original_date(value: str) -> Union[date, None]:
        assert value, '日期不能为空'
        value = datetime.strptime(value, "%Y-%m-%d").date()
        return value

    @staticmethod  # 将前端传递的CST日期解析为UTC时间对象
    def utc_date(value: str) -> Union[datetime, None]:
        if not value:
            return None
        value = datetime.strptime(value, "%Y-%m-%d").astimezone(consts.TZ_UTC)
        return value

    @classmethod  # 将前端传递的CST日期解析为UTC时间对象
    def utc_start_date(cls, value: str):
        return cls.utc_date(value)

    @classmethod  # 将前端传递的CST日期解析为UTC时间对象, 且结束时间到当日的23:59:59
    def utc_end_date(cls, value: str):
        # 结束日期需要设置到当晚23点59分59秒
        value = cls.utc_date(value)
        if not value:
            return None
        value = value + timedelta(hours=23, minutes=59, seconds=59)
        return value

    @staticmethod  # 将前端传递的CST时间解析为CST时间对象
    def cst_datetime(value: str) -> Union[datetime, None]:
        if not value:
            return None
        value = datetime.strptime(value, "%Y-%m-%d %H:%M:%S").astimezone(consts.TZ_CST)
        return value

    @staticmethod  # 将前端传递的CST日期解析为CST时间对象
    def cst_date(value: str) -> Union[datetime, None]:
        if not value:
            return None
        try:
            value = datetime.strptime(value, "%Y-%m-%d").astimezone(consts.TZ_CST)
        except:
            value = datetime.strptime(value, "%Y-%m-%d  %H:%M:%S").astimezone(consts.TZ_CST)
        return value

    @classmethod  # 将前端传递的CST日期解析为CST时间对象
    def cst_start_date(cls, value: str):
        return cls.cst_date(value)

    @classmethod  # 将前端传递的CST日期解析为CST时间对象, 且结束时间到当日的23:59:59
    def cst_end_date(cls, value: str):
        # 结束日期需要设置到当晚23点59分59秒
        value = cls.cst_date(value)
        if not value:
            return None
        value = value + timedelta(hours=23, minutes=59, seconds=59)
        return value

    @staticmethod
    def decimal(value: Any) -> Decimal:
        if not value:
            return Decimal(0)
        try:
            value = Decimal(value=str(value))
        except Exception as e:
            raise ValueError("精确类型数字解析错误")
        return value

    @staticmethod  # 从查询字符串参数中获取布尔值
    def boolean_by_args(value: str):
        if value is None:
            return None
        if value == "":
            return None
        return value == "true"

    @staticmethod  # 从查询字符串参数中获取布尔值 - 增强版本
    def bool_by_args_enhance(value: str) -> Union[None, bool]:
        if not value:
            return None
        return True if value in ["true", "True", "TRUE"] else False

    @staticmethod  # 从查询字符串参数中获取数组(列表)参数
    def array_by_args(value: str) -> List[Any]:
        if not value:
            return []
        value = ast.literal_eval(value)  # 使用安全eval方式防止代码注入
        assert isinstance(value, list), f"参数错误，期望Array类型，得到了{type(value)}类型"
        return value

    @classmethod  # 从查询字符串参数中获取整型数组(列表)参数
    def int_array_by_args(cls, value: str) -> List[int]:
        value = cls.array_by_args(value)
        for i in value:
            assert isinstance(i, int), "数据类型错误, 要求数组的每个元素都是Integer类型"
        return value

    @classmethod  # 从查询字符串参数中获取字符串数组(列表)参数
    def str_array_by_args(cls, value: str) -> List[str]:
        value = cls.array_by_args(value)
        for i in value:
            assert isinstance(i, str), "数据类型错误, 要求数组的每个元素都是String类型"
        return value

    @staticmethod  # ERP统一的附件数据对象格式
    def attach_file(value: List[Dict[str, str]]) -> List[Dict[str, str]]:
        if not value:
            return []
        assert isinstance(value, list), f"【附件】参数错误，期望Array[Object]类型，得到了{type(value)}类型"
        useful_value = list()
        for item in value:
            assert isinstance(item, dict), "【附件】参数错误，元素数据类型错误"
            filename = item.pop("Filename")
            assert filename and isinstance(filename, str), "【附件】参数错误，元素的Filename字段必传，且数据类型为string"
            file_link = item.pop("FileLink")
            assert file_link and isinstance(file_link, str), "【附件】参数错误，元素的FileLink字段必传，且数据类型为string"
            useful_value.append(
                {"Filename": filename, "FileLink": file_link}
            )
        return useful_value

    @staticmethod
    def pretty_excel_row(row: Dict[str, Any]) -> Dict[str, Any]:
        # 对非None的值做统一去除两边的空白字符串, 并去除掉Key中的星号
        keys = [i for i in row.keys() if i]
        for k in keys:
            value = row.pop(k)
            key = k.replace("*", "")
            if value is not None:
                value = str(value).strip()
            row[key] = value
        return row

    @staticmethod
    def int_gt_0(value: Any) -> int:
        if not value:
            raise ValueError("请填写大于0的数")
        try:
            value = int(value)
        except Exception as e:
            raise ValueError("int类型解析错误")
        assert 0 < value <= 2147483647, '请填写大于0的整数'
        return value

    @staticmethod
    def float_gt_0(value: Any) -> float:
        if not value:
            raise ValueError("请填写大于0的数")
        try:
            value = float(value)
        except Exception as e:
            raise ValueError("float类型解析错误")
        assert value > 0, '请填写大于0的数'
        return value

    @staticmethod
    def float_gt_eq_0(value: Any) -> float:
        if value is None:
            raise ValueError("请填写大于0的数")
        try:
            value = float(value)
        except Exception as e:
            raise ValueError("float类型解析错误")
        assert value >= 0, '请填写大于0的数'
        return value

    @staticmethod
    def decimal_gt_0(value: Any) -> Decimal:
        if not value:
            raise ValueError("请填写大于0的数")
        try:
            value = Decimal(value=str(value))
        except Exception as e:
            raise ValueError("精确类型数字解析错误")
        assert value > 0, '请填写大于0的数'
        return value

    @staticmethod
    def int_ge_0(value: Any) -> int:
        if not value:
            return 0
        try:
            value = int(value)
        except Exception as e:
            raise ValueError("int类型解析错误")
        assert 0 <= value <= 2147483647, '请填写正整数'
        return value

    @staticmethod
    def float_ge_0(value: Any) -> float:
        if not value:
            return float(0)
        try:
            value = float(value)
        except Exception as e:
            raise ValueError("float类型解析错误")
        assert value >= 0, '请填写正数'
        return value

    @staticmethod
    def decimal_ge_0(value: Any) -> Decimal:
        if not value:
            return Decimal()
        try:
            value = Decimal(value=str(value))
        except Exception as e:
            raise ValueError("精确类型数字解析错误")
        assert value >= 0, '请填写正数'
        return value

    @staticmethod
    def arithmetic_operator(value: str) -> str:
        """
        运算符校验 （大于/等于/小于/大于等于/小于等于）
        @param value:
        @return:
        """
        if not value:
            return ""
        if value not in ["gt", "eq", "lt", "ge", "le"]:
            raise ValueError(f"不支持的运算符, 支持：gt|eq|lt|ge|le")
        return value

    @staticmethod
    def order_by(value: str):
        """
        排序校验
        字段-升序/倒叙
        @param value:
        @return:
        """
        if not value:
            return ""
        _f, _sort = value.split("-")
        if _sort.lower() not in ["desc", "asc"]:
            raise ValueError(f"不支持的排序规则, 支持：desc|asc")
        return value

    # int类型列表
    @staticmethod
    def list_int_array(values: List[int]) -> List[int]:
        if not values:  # noqa
            return []
        if not isinstance(values, list):
            raise ValueError(f'列表参数类型错误, 应为list类型; 得到一个{type(values)}')
        for i, value in enumerate(values):
            if not isinstance(value, int):
                raise ValueError(f'列表第{i + 1}个参数类型错误, 应为int类型; 得到一个{type(value)}')
        return values

    # str类型列表
    @staticmethod
    def list_str_array(values: List[str]) -> List[str]:
        if not values:  # noqa
            return []
        if not isinstance(values, list):
            raise ValueError(f'列表参数类型错误, 应为list类型; 得到一个{type(values)}')
        for i, value in enumerate(values):
            if not isinstance(value, str):
                raise ValueError(f'列表第{i + 1}个参数类型错误, 应为str类型; 得到一个{type(value)}')
        return values

    # Dict类型列表
    @staticmethod
    def list_dict_array(values: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        if not values:
            return []
        if not isinstance(values, list):
            raise ValueError(f'列表参数类型错误, 应为list类型; 得到一个{type(values)}')
        for i, value in enumerate(values):
            if not isinstance(value, dict):
                raise ValueError(f'列表第{i + 1}个参数类型错误, 应为dict类型; 得到一个{type(value)}')
        return values


def with_parser(parser: reqparse.RequestParser):
    """
    装饰器：用于给 Resource 方法自动挂载 parser 和 Swagger 文档
    """

    def decorator(func):
        func.__apidoc__ = getattr(func, '__apidoc__', {})
        func.__apidoc__['expect'] = [parser]
        return func

    return decorator
